﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Diagnostics;
using MoneyWentWhere.DataModel.Binding;

namespace MoneyWentWhere.DataModel
{
    [TypeConverter(typeof(AccountTypeConverter))]
    [DataContract]
    public class Account : INotifyPropertyChanged, IExtensibleDataObject
    {
        #region Stored properties

        [DataMember(Name = "Guid")]
        Guid _guid = Guid.Empty;

        [DataMember(Name="Name")]
        string _name;

        [DataMember(Name = "Currency")]
        Currency _currency;

        [DataMember(Name = "Comment")]
        public String Comment;

        [DataMember(Name = "Transactions")]
        public TransactionList Transactions;

        #endregion

        // For serialization
        public Account() { }

        internal Account(MoneyDataSet dataSet, Guid guid, String name)
        {
            if (dataSet == null) { throw new ArgumentNullException("dataSet"); }

            DataSet = dataSet;

            _guid = guid;
            if (_guid != Guid.Empty) { DataSet.StoreObject(_guid, this); }

            Transactions = new TransactionList(this);
            Currency = dataSet.DefaultCurrency;
            Name = name;
        }

        public Transaction CreateTransaction()
        {
            return CreateTransaction(DateTime.MaxValue, TransactionKind.Normal, 0, -1);
        }

        public Transaction CreateTransaction(decimal balance)
        {
            return CreateTransaction(DateTime.MaxValue, TransactionKind.Normal, balance, -1);
        }

        public Transaction CreateTransaction(TransactionKind kind, decimal balance)
        {
            return CreateTransaction(DateTime.MaxValue, kind, balance, -1);
        }

        public Transaction CreateTransaction(
            TransactionKind kind,
            decimal amount,
            int suggestedIndex)
        {
            return CreateTransaction(DateTime.MaxValue, kind, amount, suggestedIndex);
        }

        public Transaction CreateTransaction(
            DateTime date,
            TransactionKind kind,
            decimal amount)
        {
            return CreateTransaction(date, kind, amount, -1);
        }

        public Transaction CreateTransaction(
            DateTime date,
            TransactionKind kind,
            decimal amount,
            int suggestedIndex)
        {
            Trace.Write("Creating transaction: " + kind);

            Transaction t = new Transaction(Guid.Empty, this, kind, date, Guid.Empty, amount);

            if (suggestedIndex < 0) { Transactions.Add(t); }
            else { Transactions.Insert(suggestedIndex, t); }
            
            return t;
        }

        public Guid Guid
        {
            get
            {
                if (_guid == Guid.Empty)
                {
                    _guid = Guid.NewGuid();
                    DataSet.StoreObject(_guid, this);
                }
                return _guid;
            }
        }

        public string Name 
        {
            get { return _name; }
            set
            {
                if (value == _name) { return; }
                _name = value;
                OnPropertyChanged("Name");
            }
        }

        public Currency Currency
        {
            get { return _currency; }
            set
            {
                if (value == _currency) { return; }

                _currency = value;
                if (DataSet != null) { DataSet.Currencies.Add(value); }
                OnPropertyChanged("Currency");                
            }
        }

        #region Calculated Properties

        decimal _balance;

        /// <summary>
        /// Get or set the balance.
        /// WARNING: Setting just updates the value, does not trigger reconciliation.
        /// Use carefully, or else balance may get into an inconsistent state.
        /// </summary>
        public decimal Balance 
        {
            get { return _balance; }
            internal set
            {
                if (value == _balance) { return; }
                _balance = value;
                OnPropertyChanged("Balance");
            }
        }

        /// <summary>
        /// Reconcile an account to the new balance. Add or update a Reconcile
        /// transaction to bring the balance to the new value.
        /// </summary>
        /// <param name="newBalance">Current balance on the account</param>
        /// <param name="updateLastReconcile">If last transaction is a reconcile,
        /// update it instead of adding a new one. </param>
        public void ReconcileBalance(decimal newBalance, bool updateLastReconcile)
        {
            // Find or add a reconciliation Tx
            Transaction recoTx = null;
            
            // Use last Tx if it is a Reco
            if (updateLastReconcile)
            {
                // Add opening balance if is not present
                if (Transactions.Count == 0)
                {
                    recoTx = CreateTransaction(TransactionKind.Reconcile, 0);
                    recoTx.Comment = "Opening Balance";
                }
                else
                {
                    Transaction lastTx = Transactions[Transactions.Count - 1];
                    if (lastTx.Kind == TransactionKind.Reconcile) { recoTx = lastTx; }
                }
            }

            // Create a new reconcile
            if (recoTx == null) 
            { 
                recoTx = CreateTransaction(TransactionKind.Reconcile, 0); 
            }

            // Set the value
            decimal change = newBalance - Balance;
            recoTx.SetAmount(recoTx.Amount + change); 
            recoTx.SetAccountBalance(newBalance);

            Balance = newBalance;
        }

        #endregion

        public void Delete()
        {
            // NOTE: if adding extra logic, consider putting it in 
            // AccountList.RemoveItem if appropriate.
            DataSet.Accounts.Remove(this);
        }

        public override string ToString()
        {
            return Name + " (" + Currency.Id + ")";
        }

        #region Common Serialization / Notification methods
        public event PropertyChangedEventHandler PropertyChanged;

        void OnPropertyChanged(String name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        [Browsable(false)]
        public ExtensionDataObject ExtensionData { get; set; }

        [Browsable(false)]
        public MoneyDataSet DataSet { get; internal set; }
        #endregion
    }
}
